#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<ctype.h>

#include<dirent.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<arpa/inet.h>

#define MAXSIZE 2048 
#define DEFAULTPATH "./opt" 

int get_line(int connfd, char* buf, int len);
int init_listener(int port, int epfd);
void user_accept(int listenfd, int epfd);
void user_read(int connfd, int epfd);
void epoll_run(int port);

// MAIN
int main(int argc, char* argv[])
{
	int port = atoi(argv[1]);
	int ret = chdir(DEFAULTPATH);
	if(ret == -1)
	{
		perror("chdir err");
		exit(1);
	}
	epoll_run(port);

	
	return 0;
}

// c版本取单行协议字
int get_line(int cfd, char* buf, int size)
{
	int i = 0, n;
	char c = '\0';
	while((i < size-1) && (c != '\n'))
	{
		n = recv(cfd, &c, 1, 0);
		if(n > 0)
		{
			if(c == '\r')
			{
				n = recv(cfd, &c, 1, MSG_PEEK);
				if((n > 0) && (c == '\n'))
					recv(cfd, &c, 1, 0);
				else 
					c = '\n';
			}
			buf[i] = c;
			i++;
		}
		else 
			c = '\n';
	}
	buf[i] = '\0';

	if(-1 == n)
		i = n;

	return i;
}

// 监听套接字创建
int init_listener(int port, int epfd)
{
	int ret;
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
		
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);

	// 端口复用 
	int opt = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, (socklen_t)sizeof(opt));

	ret = bind(listenfd, (struct sockaddr*)&addr, sizeof(addr));
	if(-1 == ret)
	{
		perror("bind");
		exit(1);
	}
	listen(listenfd, 128);
	
	// 挂载监听事件
	struct epoll_event ev;
	ev.data.fd = listenfd;
	ev.events = EPOLLIN;

	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd ,&ev);
	if(-1 == ret)
	{
		perror("epoll_ctl");
		exit(1);
	}

	return listenfd;
}

// 接受客户端请求
void user_accept(int listenfd, int epfd)
{
	struct sockaddr_in cli_addr;
	socklen_t addrlen = sizeof(cli_addr);
	int connfd = accept(listenfd, (struct sockaddr*)&cli_addr, &addrlen);
	
	char client_ip[64] = {0};
	printf("New Client IP: %s, PORT: %d\n", inet_ntop(AF_INET, &cli_addr.sin_addr, client_ip, sizeof(client_ip)), ntohs(cli_addr.sin_port));
	
	// 设置非阻塞
	int flag = fcntl(connfd, F_GETFL);
	flag |= O_NONBLOCK;
	fcntl(connfd, F_SETFL, flag);
	
	// 挂载事件到ep上	
	struct epoll_event ev;
	ev.data.fd = connfd;
	ev.events = EPOLLIN | EPOLLET;
	epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
	
}

// 断连反应封装
void disconnected(int connfd, int epfd)
{
	printf("Connection Closed.\n");
	int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
	if(ret)
	{
		perror("epoll_ctl");
		exit(1);
	}
	close(connfd);
}

// 回发错误页面
void send_err(int cfd, int status, char* title, char* text)
{
	char buf[4096] = {0};

	sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", status, title);
	sprintf(buf+strlen(buf), "Content-Type:%s\r\n", "text/gtml");
	sprintf(buf+strlen(buf), "Content-Length:%d\r\n", -1);
	sprintf(buf+strlen(buf), "Connection: close\r\n");
	send(cfd, buf, strlen(buf), 0);
	send(cfd, "\r\n", 2, 0);

	memset(buf, 0, sizeof(buf));

	sprintf(buf, "<html><head><title>%d %s</title></head>\n", status, title);
	sprintf(buf+strlen(buf), "<body bgcolor=\"#cc99cc\"><h2 align=\"center\">%d %s</h2>\n", status, text);
	sprintf(buf+strlen(buf), "%s\n", text);
	sprintf(buf+strlen(buf), "<hr>\n</body>\n</html>\n");
	send(cfd, buf, strlen(buf), 0);
}

// 文件类型识别
const char* get_type(const char* file)
{
	char* ret;
	ret = strrchr(file, '.');
	if(ret == NULL)
		return "text/plain; charset=utf-8";
	if(strcmp(ret, ".html") == 0 || strcmp(ret, ".htm") == 0)
		return "text/html; charset=utf-8";
	if(strcmp(ret, ".jpeg") == 0 || strcmp(ret, ".jpg") == 0)
		return "image/jepg";
	if(strcmp(ret, ".gif") == 0)
		return "image/gif";
	if(strcmp(ret, ".png") == 0)
		return "image/png";
	if(strcmp(ret, ".css") == 0)
		return "text/css";
	if(strcmp(ret, ".au") == 0)
		return "audio/basic";
	if(strcmp(ret, ".wav") == 0)
		return "audio/wav";
	if(strcmp(ret, ".mp3") == 0)
		return "audio/mpeg";
	if(strcmp(ret, ".midi") == 0)
		return "audio/midi";
	if(strcmp(ret, ".avi") == 0)
		return "video/x-msvideo";
	if(strcmp(ret, ".mov") == 0)
		return "video/quicktime";
	
	return "text/plain; charset=utf-8";

}


// 回发协议应答 
void send_respond(int cfd, int err_no, const char* disp, const char* type, int len)
{
	char buf[1024];
	sprintf(buf, "HTTP/1.1 %d %s\r\n", err_no, disp);
	sprintf(buf+strlen(buf), "Content-Type:%s\r\n", type);
	sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
	send(cfd, buf, strlen(buf), 0);
	send(cfd, "\r\n", 2, 0);
}

// 回发文件
void send_file(int cfd, const char* file)
{
	int n = 0, ret;
	char buf[1024];

	int fd = open(file, O_RDONLY);
	if(fd == -1)
	{
		send_err(cfd, 404, "NOT FOUND", "No such file or direntry");
		exit(1);
	}

	while((n = read(fd, buf, sizeof(buf))) > 0)
	{
		ret = send(cfd, buf, n, 0);
		if(ret == -1)
		{
			if(errno == EAGAIN)continue;
			else if(errno == EINTR)continue;	
			else 
			{
				perror("send err");
				exit(1);
			}
		}
	}

	close(fd);
}

// 16 - 10
int hexit(char c)
{
	if(c >= '0' && c <= '9')
		return c - '0';
	if(c >= 'a' && c <= 'z')
		return c - 'a';
	if(c >= 'A' && c <= 'Z')
		return c - 'Z';

	return 0;
}

// 转码 
void encode_str(char* to, int tosize, const char* from)
{
	int tolen;

	for(tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from)
	{
		if(isalnum(*from) || strchr("/_.-~", *from) != (char*)0)
		{
			*to = *from;
			++to;
			++tolen;
		}
		else
		{
			sprintf(to, "%%%02x", (int)*from & 0xff);
			to += 3;
			tolen += 3;
		}
	}
	*to = '\0';
}

// 解码
void decode_str(char* to, const char* from)
{
	for( ; *from != '\0'; ++to, ++from)
	{
		if(from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
		{
			*to = hexit(from[1])*16 + hexit(from[2]);
			from += 2;
		}
		else 
			*to = *from;	
	}
	*to = '\0';
}

// 回发目录
void send_dir(int cfd, const char* dirname)
{
	int i, ret;

	// 拼接一个html页面
	char buf[4096] = {0};

	sprintf(buf, "<html><head><title>目录名: %s</title></head>", dirname);
	sprintf(buf+strlen(buf), "<body><h1>当前目录: %s</h1><table>", dirname);

	char enstr[1024] = {0};
	char path[1024] = {0};

	struct dirent** ptr;
	int num = scandir(dirname, &ptr, NULL, alphasort);

	// 递归遍历目录
	for(i = 0; i < num; i++)
	{
		char* name = ptr[i]->d_name;

		sprintf(path, "%s/%s", dirname, name);
		printf("path = %s ===================\n", path);

		struct stat st;
		stat(path, &st);

		encode_str(enstr, sizeof(enstr), name);
	
		if(S_ISREG(st.st_mode))		
			sprintf(buf + strlen(buf), 
					"<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
					enstr, name, (long)st.st_size);
		else if(S_ISDIR(st.st_mode))
			sprintf(buf + strlen(buf),
					"<tr><td><a herf=\"%s/\">%s/</a></td><td>%ld</td></tr>",
					enstr, name, (long)st.st_size);

		ret = send(cfd, buf, strlen(buf), 0);
		if(-1 == ret)
		{
			if(errno == EAGAIN)
			{
				perror("send err:");
				continue;
			}
			else if(errno == EINTR)
			{
				perror("send err:");
				continue;
			}
			else 
			{
				perror("send err:");
				exit(1);
			}
		}

		memset(buf, 0, sizeof(buf));
	}

	sprintf(buf+strlen(buf), "</table></body></html>");
	send(cfd, buf, strlen(buf), 0);

	printf("dir message sent!\n");

#if 0
	// Open Dir 
	DIR* dir = opendir(dirname);
	if(dir == NULL)
	{
		perror("opendir err");
		edxit(1);
	}

	// Read Dir
	struct dirent* ptr = NULL;
	while((ptr = readdir(dir)) != NULL)
		char* name = ptr->d_name;

	close(dir);
#endif
}

// 响应请求
void http_request(int cfd, const char* request)
{
	struct stat sbuf;

	char method[16], path[256], protocol[16];

	// 以空格为标志 通过正则表达式匹配字符串
	sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
	printf("method: %s; path: %s; protocol: %s\n", method, path, protocol);
	/*	while(0)
		{
		char chk[1023];
		len = get_line(connfd, chk, sizeof(chk));
		if(len < -1 || chk[0] == '\n')break;
		printf("---len = %d, %s", len, chk);
		sleep(0);
		}
	*/

	// 转码
	decode_str(path, path);

	char* file = path+1;		// 取出文件名

	// 默认无输入访问为目录
	if(strcmp(path, "/") == 0)
	{
		file = "./";
	}
	
	int ret = stat(file, &sbuf);
	if(-1 == ret)
	{
		send_err(cfd, 404, "NOT FOUND", "No such file or direntry");
		return;
	}

	// 判断文件类型 并根据类型作出对应应答
	if(S_ISREG(sbuf.st_mode))		// 普通文件
	{
		printf("---Its a File.\n");
		send_respond(cfd, 200, "OK", get_type(file), sbuf.st_size);
		send_file(cfd, file);
	}
	else if(S_ISDIR(sbuf.st_mode))
	{
		printf("---Its a Dir.\n");
		send_respond(cfd, 200, "OK", get_type(".html"), -1);
		send_dir(cfd, file);

	}

}

// 拆分协议内容 
void user_read(int connfd, int epfd)
{ 
	char buf[1024];
	
	int len = get_line(connfd, buf, sizeof(buf));
	if(len > 0)
	{
		printf("=========== 请求头 ==========");
		printf("---%s\n", buf);

		while(1)
		{
			char nbuf[1024] = {0};
			len = get_line(connfd, nbuf, sizeof(nbuf));
			if(nbuf[0] == '\n')
				break;
			else if(-1 == len)
				break;
			printf("========== END ==========");
		}
	}
	else 
	{
		printf("Connection closed.\n");
		disconnected(connfd, epfd);
	}
	if(strncasecmp("GET", buf, 3) == 0)
	{
		http_request(connfd, buf);
		disconnected(connfd, epfd);
	}
}

// epoll事件树运行
void epoll_run(int port)
{
	struct epoll_event all_events[MAXSIZE];
	int i;

	// 创建ep的epoll事件树
	int epfd = epoll_create(MAXSIZE);
	int listenfd = init_listener(port, epfd);
	int ret;

	while(1)
	{
		ret = epoll_wait(epfd, all_events, MAXSIZE, -1);
		if(-1 == ret)
		{
			perror("epoll_wait");
			exit(1);
		}

		for(i = 0; i < ret; i++)
		{
			// 对读事件进行响应
			struct epoll_event* ev = &(all_events[i]);
			if(ev->data.fd & EPOLLIN)break;
			if(ev->data.fd == listenfd)
				user_accept(ev->data.fd, epfd);
			else 
				user_read(ev->data.fd, epfd);
		}
	}

}
